package YAPclient;

import java.awt.Color;
import java.awt.Polygon;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;

import javax.swing.Timer;

import YAPenvironment.YAPconstant;
import YAPenvironment.YAPconstant.ModState;
import YAPenvironment.YAPconstant.SideState;

import YAPserver.gameObjects.Line;
import YAPserver.gameObjects.Point;



/**
 * @author Xaro <br>
 * This class contains information about: The current lobby, player information associated with the current lobby,
 * Drawing information.
 * It also calculates edges for better drawing and handles the {@link GameMode} used in {@link Playground}.
 */

public class GameInformation {
	/**
	 * @author Xaro
	 * This enum determines how the {@link Playground} is being drawn. There are 4 different modes:<br>
	 * <b>lobbyOverview:</b> Is being used while selecting a server. It only paints a rough field. <br>
	 * <b>lobby:</b> Draws better looking walls, enables the possibility to pick a side<br>
	 * <b>game:</b> Draws walls depending on {@link SideState}, mods, pongs, etc
	 */
	public static  enum GameMode { lobbyOverview, lobby, game, none }
	/**
	 * @author Xaro
	 * Class to store information for painting ball-like structures (pongs and mods)
	 */
	public class Ball{
		// Ball position
		public Point p;
		// Ball radius
		public int r;
		// Ball color
		public Color c;
		public Ball(int radius, Point pos, Color col){
			p = pos;
			r = radius;
			c = col;
		}
	}
	/**
	 * @author Xaro
	 * Class to store information for painting wall-like structures (sliders, walls) <br>
	 * It also calculates the polygons for pick-side feature.
	 */
	public class Side{
		
		/**
		 * Stores the {@link SideState} of the side 
		 */
		public YAPconstant.SideState sState = SideState.none;
		// Different Lines for different drawings
		public Line lMid;
		public Line lOuter;
		public Line lOuterNormal; // Outer position but same length as lMid
		public Line lInner;
		// Polygons
		public Polygon pWall;
		public Polygon pMod;
		public Polygon pEdge; // End-Edge Polygon
		// Color
		public Color cCurr;
		public Color cOrig;
		public Side(Line li, Color co){
			lMid=li;
			cCurr=co;
			cOrig=co;
			calcSecondaryLines();
		}
		
		/**
		 * Calculate the polygons for painting and pickSide feature
		 */
		public void calcPolygons(){
			Polygon polyWall = new Polygon();
			polyWall.addPoint((int)Math.round(lInner.getStartPoint().getXPos()), (int)Math.round(lInner.getStartPoint().getYPos()));
			polyWall.addPoint((int)Math.round(lInner.getEndPoint().getXPos()), (int)Math.round(lInner.getEndPoint().getYPos()));
			polyWall.addPoint((int)Math.round(lOuter.getEndPoint().getXPos()), (int)Math.round(lOuter.getEndPoint().getYPos()));
			polyWall.addPoint((int)Math.round(lOuter.getStartPoint().getXPos()), (int)Math.round(lOuter.getStartPoint().getYPos()));
			pWall = polyWall;
			Polygon polyMod = new Polygon();
			polyMod.addPoint((int)Math.round(lMid.getStartPoint().getXPos()), (int)Math.round(lMid.getStartPoint().getYPos()));
			polyMod.addPoint((int)Math.round(lMid.getEndPoint().getXPos()), (int)Math.round(lMid.getEndPoint().getYPos()));
			polyMod.addPoint((int)Math.round(lInner.getEndPoint().getXPos()), (int)Math.round(lInner.getEndPoint().getYPos()));
			polyMod.addPoint((int)Math.round(lInner.getStartPoint().getXPos()), (int)Math.round(lInner.getStartPoint().getYPos()));
			pMod = polyMod;
		}
		
		
		/**
		 * Calculates the edge polygon between End-Edge of this side and Start-Edge of the input side.
		 * @param s Side
		 */
		public void calcEdgePolygons(Side s){
			Polygon polyEdge = new Polygon();
			polyEdge.addPoint((int)Math.round(lInner.getEndPoint().getXPos()),(int)Math.round(lInner.getEndPoint().getYPos()));
			polyEdge.addPoint((int)Math.round(s.lOuterNormal.getStartPoint().getXPos()),(int)Math.round(s.lOuterNormal.getStartPoint().getYPos()));
			polyEdge.addPoint((int)Math.round(s.lOuter.getStartPoint().getXPos()),(int)Math.round(s.lOuter.getStartPoint().getYPos()));
			polyEdge.addPoint((int)Math.round(lOuterNormal.getEndPoint().getXPos()),(int)Math.round(lOuterNormal.getEndPoint().getYPos()));
			pEdge = polyEdge;
		}
		
		/**
		 * Calculates the inner and outer Line. These lines got the same gradient but are {@link YAPconstants.C_LINETHICKNESS}/2 points nearer/farer from the middle.
		 *
		 */
		public void calcSecondaryLines(){
			if (lMid != null){
				Point s = lMid.getStartPoint();
				Point e = lMid.getEndPoint();
				// Norm to lMid-Line
				Point pNorm = new Point(-(e.getYPos()-s.getYPos())/lMid.getLength(), (e.getXPos()-s.getXPos())/lMid.getLength());
				int t = YAPconstant.C_LINETHICKNESS/2;
				Point newS1 = new Point(s.add(new Point(pNorm.getXPos()*t, pNorm.getYPos()*t)));
				Point newS2 = new Point(s.add(new Point(-pNorm.getXPos()*t, -pNorm.getYPos()*t)));
				lInner = new Line( new Point(s.getXPos(),s.getYPos()), new Point(e.getXPos(), e.getYPos()));
				if (mid.distance(newS1) <= mid.distance(newS2)){
					lMid = new Line(new Point(s.add(new Point(-pNorm.getXPos()*t, -pNorm.getYPos()*t))), new Point(e.add(new Point(-pNorm.getXPos()*t, -pNorm.getYPos()*t))));
					//lMid = new Line(newS1, new Point(e.add(new Point(pNorm.getXPos()*t, pNorm.getYPos()*t))));
					lOuter = new Line(newS2.add(new Point(-pNorm.getXPos()*t, -pNorm.getYPos()*t)), new Point(e.add(new Point(-pNorm.getXPos()*2*t, -pNorm.getYPos()*2*t))));
					lOuterNormal = new Line(newS2.add(new Point(-pNorm.getXPos()*t, -pNorm.getYPos()*t)), new Point(e.add(new Point(-pNorm.getXPos()*2*t, -pNorm.getYPos()*2*t))));
				}
				else{
					lMid = new Line(new Point(s.add(new Point(pNorm.getXPos()*t, pNorm.getYPos()*t))), new Point(e.add(new Point(pNorm.getXPos()*t, pNorm.getYPos()*t))));
//					lMid = new Line(newS2, new Point(e.add(new Point(-pNorm.getXPos()*t, -pNorm.getYPos()*t))));
					lOuter = new Line(newS1.add(new Point(pNorm.getXPos()*t, pNorm.getYPos()*t)), new Point(e.add(new Point(pNorm.getXPos()*2*t, pNorm.getYPos()*2*t))));
					lOuterNormal = new Line(newS1.add(new Point(pNorm.getXPos()*t, pNorm.getYPos()*t)), new Point(e.add(new Point(pNorm.getXPos()*2*t, pNorm.getYPos()*2*t))));
				}
			}
		}
		
		/**
		 * Calculates the Lines from intersection to intersection
		 * @param side which should be compared with
		 */
		public void calcIntersectSecondaryLines(Side side) {
			Point p = side.lInner.intersect(lInner);
			if (p!=null){
				side.lInner.getStartPoint().setXYPos(p.getXPos(), p.getYPos());
				lInner.getEndPoint().setXYPos(p.getXPos(), p.getYPos());
			}
			p = side.lOuter.intersect(lOuter);
			if (p!=null){
				side.lOuter.getStartPoint().setXYPos(p.getXPos(), p.getYPos());
				lOuter.getEndPoint().setXYPos(p.getXPos(), p.getYPos());
			}
		}
	}

	private String m_Name = "";
	private String m_WinnerName = "";
	private int m_MaxPlayerCount = 0;
	private int m_CurrentPlayerCount = 0;
	private GameMode m_GameMode;
	private ArrayList<Side> sides = new ArrayList<Side>();
	private ArrayList<Side> sidesPG = new ArrayList<Side>();
	private ArrayList<Ball> balls = new ArrayList<Ball>();
	private ArrayList<Ball> ballsPG = new ArrayList<Ball>();
	private ArrayList<Line> sliders = new ArrayList<Line>();
	private ArrayList<Line> slidersPG = new ArrayList<Line>();
	private ArrayList<Ball> mods = new ArrayList<Ball>();
	private ArrayList<Ball> modsPG = new ArrayList<Ball>();
	private boolean bChangedSideData=false;
	private boolean bChangedBallData=false;
	private boolean bChangedSliderData=false;
	private boolean bChangedModData=false;
	private double m_CurrAngle=0;
	private double m_PlayerAngle=0;
	private int m_OwnSideIndex;
	private Point mid = new Point(YAPconstant.C_GAMESIZE/2, YAPconstant.C_GAMESIZE/2);
	private boolean m_playerIsHost = false;
	private int m_StartCounter = 0;
	private int m_CurrentLives = 0;
	private boolean m_IsPaused = false;
	private boolean m_IsEndingGame = false;
	private Timer timEndingGame;
	
	/**
	 * Standard dummy-constructor
	 */
	public GameInformation(){}
	
	/**
	 * @param name The name of the Lobby
	 * @param curPlrCnt Current player count in the lobby
	 * @param maxPlrCnt Maximum players in lobby
	 */
	public GameInformation(String name, int curPlrCnt, int maxPlrCnt) {
		setName(name);
		setCurrentPlayerCount(curPlrCnt);
		setMaxPlayerCount(maxPlrCnt);
	}

	/**
	 * Ends the game, initiates the winner string output in the Playground and finishes the game
	 */
	public void gameEnded() {
		setEndingGame(true);
		ActionListener aL = new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent e) {
				DataModul.gInfo.setGameMode(GameMode.lobby);
				DataModul.repaintFrames();
				if (timEndingGame != null){
					timEndingGame.stop();
					timEndingGame = null;
				}
			}
		};
		
		timEndingGame = new Timer(YAPconstant.C_ENDINGTIME, aL);
		timEndingGame.setRepeats(false);
		timEndingGame.start();
	}

	/**
	 * Resets all the data.
	 */
	public synchronized void reset(){
		sides.clear();
		sidesPG.clear();
		balls.clear();
		ballsPG.clear();
		sliders.clear();
		slidersPG.clear();
		mods.clear();
		modsPG.clear();
		DataModul.repaintFrames();
	}
	
	/**
	 * Calculates and sets the angle of the deserved side for the rotation.
	 * @param sideIndex Index of the side which should be bottom
	 */
	public synchronized void setAngle(int sideIndex){
		if (sides.size() > sideIndex){
			setPlayerAngle(Math.PI - sides.get(sideIndex).lMid.getAngle());
			setCurrAngle(getPlayerAngle());
			setOwnSideIndex(sideIndex);
		}
	}
	
	/**
	 * Sets side assignment color and own sideIndex
	 * @param strAssignments String[] with sideState information, see protocol file for more information.
	 */
	public synchronized void setSideAssignments(String[] strAssignments){
		if (strAssignments.length-1 == sides.size()){
			for (int i = 1; i < strAssignments.length; i++){
				if (strAssignments[i].equalsIgnoreCase("true") || strAssignments[i].equalsIgnoreCase("1")){
					sides.get(i-1).cCurr = YAPconstant.C_ASSIGNED_COLOR;
				}
				if ((i-1) == getOwnSideIndex()){
					sides.get(i-1).cCurr = YAPconstant.C_OWNASSIGNED_COLOR;
				}
			}
		}
	}
	
	/** 
	 * Sets side states defined in {@link SideState}
	 * @param s String[] with sideState information, see protocol file for more information.
	 */
	public synchronized void setSideState(String[] s){
		if ((DataModul.gInfo != null) && (!DataModul.gInfo.isEndingGame())){
			if (s.length >= sides.size()){
				for (int i = 0; i<sides.size();i++){
					if (s[i].trim() != ""){
						sides.get(i).sState = SideState.getEnumByInt(Integer.parseInt(s[i]));
					}
				}
			}
			bChangedSideData = true;
		}
	}
	
	/** 
	 * Adds the new side data from the server and calculates
	 * the polygon for the paint method. This method is only used through getLobbyInformation, so it doesn't
	 * need any calculations during the game.
	 * @param s String[] with side information, see protocol file for more information.
	 */
	public synchronized void setSides(String[] s){
		if (DataModul.gInfo != null){
			sides.clear();
			for(int i=0; i<s.length-3; i+=2){
				if (i+3 < s.length){
					sides.add(new Side(new Line(new Point(Integer.parseInt(s[i]),Integer.parseInt(s[i+1])),new Point(Integer.parseInt(s[i+2]),Integer.parseInt(s[i+3]))), YAPconstant.C_WALL_COLOR));
				}
			}
			if (s.length >= 2){
				sides.add(new Side(new Line(new Point(Integer.parseInt(s[s.length-2]),Integer.parseInt(s[s.length-1])),new Point(Integer.parseInt(s[0]),Integer.parseInt(s[1]))), YAPconstant.C_WALL_COLOR));
			}
			
			for (int i = 0; i < sides.size(); i++){
				if (i == sides.size()-1){
					sides.get(i).calcIntersectSecondaryLines(sides.get(0));
				}
				else{
					sides.get(i).calcIntersectSecondaryLines(sides.get(i+1));
				}
			}
			for (int i = 0; i < sides.size(); i++){
				if (i == sides.size()-1){
					sides.get(i).calcEdgePolygons(sides.get(0));
				}
				else{
					sides.get(i).calcEdgePolygons(sides.get(i+1));
				}
			}
			for (int i = 0; i < sides.size(); i++){
				sides.get(i).calcPolygons();
			}
			
			
			bChangedSideData = true;
		}
	}

	/**
	 * Adds all the Pongs
	 * @param s String[] with ball information, see protocol file for more information.
	 */
	public synchronized void setBalls(String[] s){
		if (DataModul.gInfo != null){
			balls.clear();
			for (int i = 0; i < s.length ;i+=3){
				if (i+2 < s.length)	{
					balls.add(new Ball(Integer.parseInt(s[i+2]), new Point(Integer.parseInt(s[i]), Integer.parseInt(s[i+1])), YAPconstant.C_PONG_COLOR));
				}
			}
			bChangedBallData = true;
		}
	}

	/**
	 * Adds all the sliders
	 * @param strSliders String[] with slider information, see protocol file for more information.
	 */
	public synchronized void setSliders(String[] strSliders){
		if (DataModul.gInfo != null){
			sliders.clear();
			
			for (int i = 0; i < strSliders.length ;i+=4){ 
				if (i+3 < strSliders.length){
					Line l = new Line(new Point(Integer.parseInt(strSliders[i]), Integer.parseInt(strSliders[i+1])), new Point(Integer.parseInt(strSliders[i+2]), Integer.parseInt(strSliders[i+3])));
 					l = placeLineBack(l, YAPconstant.C_LINETHICKNESS/2, mid);
					if (l != null){
						sliders.add(l);
					}
				}
			}
			
			bChangedSliderData = true;
		}
	}

	/**
	 * Adds all the mods
	 * @param s String[] with mod information, see protocol file for more information.
	 */
	public synchronized void setMods(String[] s){
		if (DataModul.gInfo != null){
			mods.clear();
			for (int i = 0; i < s.length ;i+=4){ 
				if (i+3 < s.length){
					mods.add(new Ball(Integer.parseInt(s[i+2]), new Point(Integer.parseInt(s[i]), Integer.parseInt(s[i+1])), ModState.getColorByInt(Integer.parseInt(s[i+3]))));
				}
			}
			bChangedModData = true;
		}
	}
	
	/**
	 * Calculates a Line with the same gradient but farther away from the middle.
	 * @param l Line which should be placed back
	 * @param points Amount in pixels the line should be placed back
	 * @param lMid Middle to determine the direction of "place back"
	 * @return Back placed Line
	 */
	private Line placeLineBack(Line l, double points, Point lMid){
		Line lNew = null;
		if (lMid != null){
			Point s = l.getStartPoint();
			Point e = l.getEndPoint();
			// Norm to l-Line
			Point pNorm = new Point(-(e.getYPos()-s.getYPos())/l.getLength(), (e.getXPos()-s.getXPos())/l.getLength());
			Point newS1 = new Point(s.add(new Point(pNorm.getXPos()*points, pNorm.getYPos()*points)));
			Point newS2 = new Point(s.add(new Point(-pNorm.getXPos()*points, -pNorm.getYPos()*points)));
			if (mid.distance(newS1) <= mid.distance(newS2)){
				lNew = new Line(newS2, new Point(e.add(new Point(-pNorm.getXPos()*points, -pNorm.getYPos()*points))));
			}
			else{
				lNew = new Line(newS1, new Point(e.add(new Point(pNorm.getXPos()*points, pNorm.getYPos()*points))));
			}
		}
		return lNew;
	}


	/**
	 * Use this method only in the PAINT method.
	 * Gets the sides for drawing
	 * @return array of sides
	 */
	public synchronized ArrayList<Side> getDrawSides(){
		if (bChangedSideData){
			ArrayList<Side> tmp = sidesPG;
			sidesPG = sides;
			sides = tmp;
			bChangedSideData = false;
		}
		return sidesPG;
	}
	
	/**
	 * Use this method only in the PAINT method.
	 * Gets the sliders for drawing
	 * @return array of sliders
	 */
	public synchronized ArrayList<Line> getDrawSliders(){
		if (bChangedSliderData){
			ArrayList<Line> tmp = slidersPG;
			slidersPG = sliders;
			sliders = tmp;
			bChangedSliderData = false;
		}
		return slidersPG;
	}

	/**
	 * Use this method only in the PAINT method.
	 * Gets the mods for drawing
	 * @return array of mods
	 */
	public synchronized ArrayList<Ball> getDrawMods(){
		if (bChangedModData){
			ArrayList<Ball> tmp = modsPG;
			modsPG = mods;
			mods = tmp;
			bChangedModData = false;
		}
		return modsPG;
	}

	/**
	 * Use this method only in the PAINT method.
	 * Gets the pongs for drawing
	 * @return array of pongs
	 */
	public synchronized ArrayList<Ball> getDrawBalls(){
		if (bChangedBallData){
			ArrayList<Ball> tmp = ballsPG;
			ballsPG = balls;
			balls = tmp;
			bChangedBallData = false;
		}
		return ballsPG;
	}
	
	/**
	 * @return Display string in LobbyList
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString(){
		return getName()+ " (" + getCurrentPlayerCount() + "/" + getMaxPlayerCount() + ")";
	}
	
	/**
	 * @return Lobbyname
	 */
	public String getName(){
		return m_Name;
	}
	
	/**
	 * @param name Lobbyname
	 */
	public void setName(String name){
		m_Name = name;
	}
	
	/**
	 * @return Maximum amount possible players in lobby
	 */
	public int getMaxPlayerCount(){
		return m_MaxPlayerCount;
	}

	/**
	 * @param maxPlayerCount Maximum amount of possible players in lobby 
	 */
	public void setMaxPlayerCount(int maxPlayerCount){
		m_MaxPlayerCount = maxPlayerCount;
	}

	/**
	 * @return Current player amount
	 */
	public int getCurrentPlayerCount(){
		return m_CurrentPlayerCount;
	}
	
	/**
	 * @param currentPlayerCount Current player amount
	 */
	public void setCurrentPlayerCount(int currentPlayerCount){
		m_CurrentPlayerCount = currentPlayerCount;
	}

	/**
	 * @return Angle that needs to be rotated to display the players side at the bottom
	 */
	public double getPlayerAngle() {
		return m_PlayerAngle;
	}

	/**
	 * @param m_PlayerAngle Angle that needs to be rotated to display the players side at the bottom
	 */
	public void setPlayerAngle(double m_PlayerAngle) {
		this.m_PlayerAngle = m_PlayerAngle;
	}

	/**
	 * @return Currently rotated angle
	 */
	public double getCurrAngle() {
		return m_CurrAngle;
	}

	/**
	 * @param m_CurrAngle Currently rotated angle
	 */
	public void setCurrAngle(double m_CurrAngle) {
		this.m_CurrAngle = m_CurrAngle;
	}

	/**
	 * @return Current {@link GameMode}
	 */
	public GameMode getGameMode() {
		return m_GameMode;
	}

	/**
	 * Sets the {@link GameMode} and resets the currentPlayerAngle, startCounter and endingGame
	 * @param m_GameMode {@link GameMode}
	 */
	public void setGameMode(GameMode m_GameMode) {
		setCurrAngle(getPlayerAngle());
		setStartCounter(0);
		setIsPaused(false);
		if (timEndingGame != null){
			timEndingGame.stop();
			timEndingGame = null;
		}
		setEndingGame(false);
		this.m_GameMode = m_GameMode;
	}

	/**
	 * @return SideIndex of the players side
	 */
	public int getOwnSideIndex() {
		return m_OwnSideIndex;
	}
	
	/**
	 * @param m_OwnSideIndex SideIndex of the players side
	 */
	public void setOwnSideIndex(int m_OwnSideIndex) {
		this.m_OwnSideIndex = m_OwnSideIndex;
	}

	/**
	 * @return True if the player is host of the current game
	 */
	public boolean playerIsHost() {
		return this.m_playerIsHost;
	}
	
	/**
	 * @param isHost Set the player as host of the current game
	 */
	public void setPlayerIsHost(boolean isHost) {
		this.m_playerIsHost = isHost;
	}

	/**
	 * @return Sets the counter till the game starts
	 */
	public int getStartCounter() {
		return m_StartCounter;
	}

	/**
	 * @param m_StartCounter Number in seconds till the game starts
	 */
	public void setStartCounter(int m_StartCounter) {
		this.m_StartCounter = m_StartCounter;
	}

	/**
	 * @return Current player lives
	 */
	public int getCurrentLives() {
		return m_CurrentLives;
	}

	/**
	 * Values below 0 wont be set
	 * @param m_CurrentLives Current player lives
	 */
	public void setCurrentLives(int m_CurrentLives) {
		if (m_CurrentLives >= 0){
			this.m_CurrentLives = m_CurrentLives;
		}
	}
	
	/**
	 * @return Name of the winner from the latest game
	 */
	public String getWinnerName() {
		return m_WinnerName;
	}

	/**
	 * @param m_WinnerName Name of the winner from the latest game
	 */
	public void setWinnerName(String m_WinnerName) {
		this.m_WinnerName = m_WinnerName;
	}

	/**
	 * @return Returns true if the game is ending
	 */
	public boolean isEndingGame() {
		return m_IsEndingGame;
	}

	/**
	 * @param m_IsEndingGame Set to true if the game is about to end (got endGame command from server)
	 */
	public void setEndingGame(boolean m_IsEndingGame) {
		this.m_IsEndingGame = m_IsEndingGame;
	}
	
	
	/**
	 * Interrupts the timer for ending. Call this method if the ending sequence should be interrupted and the playground should change to lobby-Mode
	 */
	public void interruptEnding(){
		if (timEndingGame != null){
			timEndingGame.stop();
			timEndingGame = null;
		}
		this.setGameMode(GameMode.lobby);
	}

	/**
	 * @return Returns true if the game is paused
	 */
	public boolean isPaused() {
		return m_IsPaused;
	}

	/**
	 * @param m_IsPaused Set to true if the game is paused (got pause command from server)
	 */
	public void setIsPaused(boolean m_IsPaused) {
		this.m_IsPaused = m_IsPaused;
	}
	
}
